import json
import os
from typing import Dict, List, Optional
import h5py
from pydantic import BaseModel, Field
from datetime import datetime, timedelta, timezone
from app import bn, users
from method import Dingding, Kline, Log
from config import db

class Order(BaseModel):
    user_binance: Optional[List[Dict]] = Field(default_factory=list)
    kline_binance: Optional[Dict[str, List[Dict]]] = Field(default_factory=dict)
    ticker_binance: Optional[Dict[str, Dict]] = Field(default_factory=dict)
    positions_binance: Optional[Dict[str, List[Dict]]] = Field(default_factory=dict)

# user_binance: tactics, username, balance
# balance: walletBalance, availableBalance
# tactics: symbol(小写), buyCooldown(卖出后买入冷却时间)，
# oscillatingCooldown(1小时多次卖出后买入冷却时间)，releaseCoefficient(当前交易量与MA20的交易量比例)，
# slopeThreshold(斜率，最新价比格子价格最小+-百分比)，leverage(杠杆倍率)，
# stopLoss(止损)，stopProfit(止盈)

# ticker_binance：key(symbol（大写）), symbol(小写)，lastPrice，averagePrice，priceChangePercent

# positions_binance：key(username)，symbol(大写)，positionAmt，entryPrice，breakEvenPrice，unRealizedProfit，initialMargin

# 用于记录每个币的最后一次卖出时间
sell_timestamps = {}

# 检查是否在冷却时间中
def can_buy(symbol: str, buyCooldown: int) -> bool:
    cooldown_minutes = buyCooldown or 10
    last_sell_time = sell_timestamps.get(symbol)

    if last_sell_time is None:
        return True
    if datetime.now(timezone.utc) - last_sell_time >= timedelta(minutes=cooldown_minutes):
        return True
    return False

# 卖出操作后记录时间
def record_sell(symbol: str):
    sell_timestamps[symbol] = datetime.now(timezone.utc)

# 获取币种信息，用来筛选
def get_exchangeInfo():
    data = []
    keys = 'symbols'
    if os.path.exists(db.exchange_info):
        # 读取数据文件（'r' 表示只读）
        with h5py.File(db.exchange_info, 'r') as f:
            if keys in f:
                # 读取嵌套组中的数据集
                data_list = f[keys][()]
                # 把JSON字符串反序列化回对象
                data = json.loads(data_list.decode('utf-8'))
    return data
# 获取数量和价格小数点后几位数限制，币安传参小数有限制最多位数，否则下单会失败
def get_tick_and_step(filters: list[dict]) -> tuple[float, float]:
    tick_size = None
    step_size = None
    for f in filters:
        if f.get("filterType") == "PRICE_FILTER":
            tick_size = f["tickSize"]
        elif f.get("filterType") == "LOT_SIZE":
            step_size = f["stepSize"]
    def get_precision_from_step(step_str: str) -> int:
        """根据 tickSize 或 stepSize 字符串返回允许的小数位数"""
        if '.' not in step_str:
            return 0
        return len(step_str.rstrip('0').split('.')[1])
    tick_size = get_precision_from_step(tick_size)
    step_size = get_precision_from_step(step_size)
    return tick_size, step_size

def set_order(order: Order, username: str, symbol: str):
    symbol = symbol.upper()
    index = next((i for i, item in enumerate(order['user_binance']) if item['username'] == username), -1)
    if index < 0:
        return
    user = order['user_binance'][index]
    balance = user['balance']
    tactics = user['tactics']
    tacticsItem = next((d for d in tactics if d['symbol'] == symbol.lower()), None)
    try:
        if float(balance['walletBalance']) and float(balance['availableBalance']) / float(balance['walletBalance']) > 0.5:
            # 仓位获取
            exists = next((item for item in order['positions_binance'][username] if item['symbol'] == symbol), None)
            # 币种实时信息获取
            ticker = order['ticker_binance'][symbol]
            price = float(ticker['lastPrice'])
            # K线获取
            kline = order['kline_binance'][symbol]
            klineLast = kline[-1]
            mas = Kline.Indicator(kline)
            indicator = {
                'ma7': float(mas.MA_line(7)),
                'ma25': float(mas.MA_line(25)),
            }
            # 判断是否存在仓位，存在仓位不再买入
            if exists and abs(float(exists['initialMargin'])) > 0 and can_buy(symbol, tacticsItem['buyCooldown']):
                entry_price = float(exists['entryPrice'])
                stop_price = False
                stop_ma = False
                # 判断当前是否已经到达止盈止损位置，进行平仓
                if exists['positionSide'] == 'LONG':
                    stop_price = (price - entry_price) / entry_price if (price - entry_price) / entry_price < float(tacticsItem['stopLoss']) else stop_price
                    stop_price = (price - entry_price) / entry_price if (price - entry_price) / entry_price > float(tacticsItem['stopProfit']) else stop_price
                    stop_ma = price < indicator['ma25'] * 0.999
                    if stop_ma:
                        stop_price = (indicator['ma25'] - entry_price) / entry_price 
                elif exists['positionSide'] == 'SHORT':
                    stop_price = (entry_price - price) / entry_price if (entry_price - price) / entry_price < float(tacticsItem['stopLoss']) else stop_price
                    stop_price = (entry_price - price) / entry_price if (entry_price - price) / entry_price > float(tacticsItem['stopProfit']) else stop_price
                    stop_ma = price > indicator['ma25'] * 1.001
                    if stop_ma:
                        stop_price = (entry_price - indicator['ma25']) / entry_price
                # 止损/止盈
                if stop_price and float(exists['breakEvenPrice']):
                    record_sell(symbol)
                    exchangeInfo = get_exchangeInfo()
                    info = next((item for item in exchangeInfo if item['symbol'] == symbol), None)
                    tick, step = get_tick_and_step(info['filters'])
                    Log.set_log(json.dumps({
                        'positionSide': exists['positionSide'],
                        'side': 'SELL' if exists['positionSide'] == 'LONG' else 'BUY',
                        'quantity': exists['positionAmt'],
                        'symbol': exists['symbol'],
                        'stopPrice': round(indicator['ma25'] if stop_ma else price, tick),
                        'profit': stop_price * float(tacticsItem['leverage']) * 100,
                        'amount': abs(float(exists['positionAmt'])) * stop_price * entry_price,
                        'exists': exists,
                        'indicator': indicator
                    }), 'order')
                    bn.binance_order(bn.BinanceOrder(
                        symbol=exists['symbol'],
                        side='SELL' if exists['positionSide'] == 'LONG' else 'BUY',
                        positionSide=exists['positionSide'],
                        quantity=round(abs(float(exists['positionAmt'])), step)
                    ), users.User(
                        username=username
                    ))
                    # 钉钉机器人提醒
                    title = f"{symbol} {'看多' if exists['positionSide'] == 'LONG' else '看空'}卖出平单"
                    content = (
                        f"- **数量**：`{round(abs(float(exists['positionAmt'])), step)}`\n"
                        f"- **平仓价格**：`{round(price, tick)}`\n"
                        f"- **开仓价格**：`{round(entry_price, tick)}`\n"
                        f"- **杠杆**：`{tacticsItem['leverage']}`\n"
                        f"- **收益率**：`{round(stop_price * float(tacticsItem['leverage']) * 100, 2)}%`\n"
                        f"- **收益额**：`{round(abs(float(exists['positionAmt'])) * stop_price * entry_price, 4)}`\n"
                        f"- **时间**：`{datetime.now().strftime('%Y-%m-%d %H:%M')}`\n\n"
                        f"> 价格取下单时的最新价格显示，但是下单是市价下单，收益率和收益额为计算值，因此实际会有点出入。"
                    )
                    Dingding.send_dingding_message(title, content)
                return
            else:
                if tacticsItem and can_buy(symbol, tacticsItem['buyCooldown']):
                    cross = mas.cross_line(7, 25, float(tacticsItem['slopeThreshold']))
                    quantity = float(balance['walletBalance']) / 2 / len(tactics)
                    # slope = abs((indicator['ma7'] - indicator['ma25']) / indicator['ma7']) > 0.001
                    if quantity > 50:
                        quantity = 50
                    elif quantity < 10:
                        quantity = 10
                    if cross == 'golden_cross':
                        # 金叉上涨
                        record_sell(symbol)
                        exchangeInfo = get_exchangeInfo()
                        info = next((item for item in exchangeInfo if item['symbol'] == symbol), None)
                        tick, step = get_tick_and_step(info['filters'])
                        Log.set_log(json.dumps({
                            'positionSide': 'LONG',
                            'side': 'BUY',
                            'type': 'LIMIT',
                            'symbol': symbol,
                            'quantity': round(quantity / price * float(tacticsItem['leverage']), step),
                            'price': round((price + indicator['ma7']) / 2, tick),
                            'stopPrice': price * 0.98,
                            'klineLast': klineLast,
                            'indicator': indicator
                        }), 'order')
                        bn.binance_order(bn.BinanceOrder(
                            symbol=symbol,
                            side='BUY',
                            positionSide='LONG',
                            type='LIMIT',
                            timeInForce='GTC',
                            price=round((price + indicator['ma7']) / 2, tick),
                            quantity=round(quantity / price * float(tacticsItem['leverage']), step),
                        ), users.User(
                            username=username
                        ))
                        # 钉钉机器人提醒
                        title = f"{symbol} 看多买入挂单"
                        content = (
                            f"- **数量**：`{round(quantity / price * float(tacticsItem['leverage']), step)}`\n"
                            f"- **买入价格**：`{round((price + indicator['ma7']) / 2, tick)}`\n"
                            f"- **触发价格**：`{round(price, tick)}`\n"
                            f"- **杠杆**：`{tacticsItem['leverage']}`\n"
                            f"- **止盈**：`{round(price + price * float(tacticsItem['stopProfit']), tick)}`\n"
                            f"- **止损**：`{round(price + price * float(tacticsItem['stopLoss']), tick)}`\n"
                            f"- **时间**：`{datetime.now().strftime('%Y-%m-%d %H:%M')}`\n\n"
                            f"> 止盈止损为正常的百分比计算，实际可能会有差别"
                        )
                        Dingding.send_dingding_message(title, content)
                    elif cross == 'death_cross':
                        # 死叉下跌
                        record_sell(symbol)
                        exchangeInfo = get_exchangeInfo()
                        info = next((item for item in exchangeInfo if item['symbol'] == symbol), None)
                        tick, step = get_tick_and_step(info['filters'])
                        Log.set_log(json.dumps({
                            'positionSide': 'SHORT',
                            'side': 'SELL',
                            'type': 'LIMIT',
                            'symbol': symbol,
                            'quantity': round(quantity / price * float(tacticsItem['leverage']), step),
                            'price': round((price + indicator['ma7']) / 2, tick),
                            'stopPrice': price * 1.01,
                            'klineLast': klineLast,
                            'indicator': indicator
                        }), 'order')
                        bn.binance_order(bn.BinanceOrder(
                            symbol=symbol,
                            side='SELL',
                            positionSide='SHORT',
                            type='LIMIT',
                            timeInForce='GTC',
                            price=round((price + indicator['ma7']) / 2, tick),
                            quantity=round(quantity / price * float(tacticsItem['leverage']), step),
                        ), users.User(
                            username=username
                        ))
                        # 钉钉机器人提醒
                        title = f"{symbol} 看空买入挂单"
                        content = (
                            f"- **数量**：`{round(quantity / price * float(tacticsItem['leverage']), step)}`\n"
                            f"- **买入价格**：`{round((price + indicator['ma7']) / 2, tick)}`\n"
                            f"- **触发价格**：`{round(price, tick)}`\n"
                            f"- **杠杆**：`{tacticsItem['leverage']}`\n"
                            f"- **止盈**：`{round(price - price * float(tacticsItem['stopProfit']), tick)}`\n"
                            f"- **止损**：`{round(price - price * float(tacticsItem['stopLoss']), tick)}`\n"
                            f"- **时间**：`{datetime.now().strftime('%Y-%m-%d %H:%M')}`\n\n"
                            f"> 止盈止损为正常的百分比计算，实际可能会有差别"
                        )
                        Dingding.send_dingding_message(title, content)
        else:
            # Log.set_log('余额不足！', 'error')
            return
    except Exception as e:
        Log.set_log(f"下单初始化失败: {e}", 'error')
